/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012-2013 Oliver Katz */

#include "visual.h"

namespace sleepyface
{
	namespace visual
	{
		DFBEnumerationResult enumerateInputDevice(unsigned int id, DFBInputDeviceDescription dsc, void *data)
		{
			map<unsigned int, DFBInputDeviceDescription> *devs = (map<unsigned int, DFBInputDeviceDescription> *)data;
			(*devs)[id] = dsc;
			return DFENUM_OK;
		}

		Region::Region()
		{
			x0 = y0 = x1 = y1 = 0;
		}

		Region::Region(int x, int y)
		{
			x0 = x;
			y0 = y;
			x1 = 0;
			y1 = 0;
		}

		Region::Region(int x0, int y0, int x1, int y1)
		{
			this->x0 = x0;
			this->y0 = y0;
			this->x1 = x1;
			this->y1 = y1;
		}

		int Region::getX0()
		{
			return x0;
		}

		int Region::setX0(int x)
		{
			return (x0 = x);
		}

		int Region::getY0()
		{
			return y0;
		}

		int Region::setY0(int y)
		{
			return (y0 = y);
		}

		int Region::getX1()
		{
			return x1;
		}

		int Region::setX1(int x)
		{
			return (x1 = x);
		}

		int Region::getY1()
		{
			return y1;
		}

		int Region::setY1(int y)
		{
			return (y1 = y);
		}

		int Region::getWidth()
		{
			return x1-x0;
		}

		int Region::setWidth(int w)
		{
			x1 = x0+w;
			return w;
		}

		int Region::getHeight()
		{
			return y1-y0;
		}

		int Region::setHeight(int h)
		{
			y1 = y0+h;
			return h;
		}

		bool Region::within(Region coord)
		{
			return !(coord.getX0() < x0 || coord.getX0() > x1 || coord.getY0() < y0 || coord.getY0() > y1);
		}

		bool Region::inside(Region region)
		{
			return (within(region) && (region.getX1() < x1 && region.getY1() < y1));
		}

		string Region::display()
		{
			stringstream ss;
			ss << "(" << x0 << "x" << y0 << ")-(" << x1 << "x" << y1 << ")";
			return ss.str();
		}

		Region coordBox(int x0, int y0, int x1, int y1)
		{
			return Region(x0, y0, x1, y1);
		}

		Region offsetBox(int x0, int y0)
		{
			return Region(x0, y0, 0, 0);
		}

		Region sizeBox(int w, int h)
		{
			return Region(0, 0, w, h);
		}

		Region coordSizeBox(int x0, int y0, int w, int h)
		{
			return Region(x0, y0, x0+w, y0+h);
		}

		Color::Color()
		{
			r = g = b = a = 0x00;
		}

		Color::Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
		{
			this->r = r;
			this->g = g;
			this->b = b;
			this->a = a;
		}

		Color::Color(unsigned char r, unsigned char g, unsigned char b)
		{
			this->r = r;
			this->g = g;
			this->b = b;
			this->a = 0xff;
		}

		Color::Color(string s)
		{
			stringstream ssr;
			ssr << hex << s.substr(1, 3);
			ssr >> this->r;
			stringstream ssg;
			ssr << hex << s.substr(3, 5);
			ssr >> this->g;
			stringstream ssb;
			ssr << hex << s.substr(5, 7);
			ssr >> this->b;
			stringstream ssa;
			ssa << hex << s.substr(7, 9);
			ssa >> this->a;
		}

		int Color::getR()
		{
			return r;
		}

		int Color::getG()
		{
			return g;
		}

		int Color::getB()
		{
			return b;
		}

		int Color::getA()
		{
			return a;
		}

		string Color::display()
		{
			stringstream ss;
			ss << "#" << hex << r << hex << g << hex << b << hex << a;
			return ss.str();
		}

		Surface::Surface()
		{
			region = Region();
			surf = NULL;
		}

		Surface::Surface(Region r, Renderer &renderer)
		{
			region = r;
			DFBSurfaceDescription dsc;
			dsc.flags = (DFBSurfaceDescriptionFlags)(DSDESC_CAPS | DSDESC_WIDTH | DSDESC_HEIGHT);
			dsc.caps = (DFBSurfaceCapabilities)(DSCAPS_FLIPPING);
			dsc.width = r.getWidth();
			dsc.height = r.getHeight();
			renderer.getDFB()->CreateSurface(renderer.getDFB(), &dsc, &surf);
		}

		Surface::Surface(IDirectFBSurface *s)
		{
			region = Region();
			surf = s;
			int w = 0, h = 0;
			s->GetSize(s, &w, &h);
			region.setWidth(w);
			region.setHeight(h);
		}

		Surface::~Surface()
		{
			surf->Release(surf);
		}

		Region &Surface::getRegion()
		{
			return region;
		}

		IDirectFBSurface *Surface::getIDirectFBSurface()
		{
			return surf;
		}

		int Surface::rect(Region reg, Color col)
		{
			if (surf->SetColor(surf, col.getR(), col.getG(), col.getB(), col.getA()) != DFB_OK)
				return 1;
			if (surf->FillRectangle(surf, reg.getX0(), reg.getY0(), reg.getWidth(), reg.getHeight()) != DFB_OK)
				return 2;
			return 0;
		}

		int Surface::line(Region reg, Color col)
		{
			if (surf->SetColor(surf, col.getR(), col.getG(), col.getB(), col.getA()) != DFB_OK)
				return 1;
			if (surf->DrawLine(surf, reg.getX0(), reg.getY0(), reg.getX1(), reg.getY1()) != DFB_OK)
				return 2;
			return 0;
		}

		int Surface::text(Renderer renderer, Region reg, Color col, string f, string s)
		{
			if (surf->SetFont(surf, renderer.getFont(f)) != DFB_OK)
				return 1;
			if (surf->SetColor(surf, col.getR(), col.getG(), col.getB(), col.getA()) != DFB_OK)
				return 2;
			if (surf->DrawString(surf, s.c_str(), -1, reg.getX0(), reg.getY0(), DSTF_LEFT) != DFB_OK)
				return 3;
			return 0;
		}

		int Surface::renderTo(Surface s, Region myreg, Region theirreg)
		{
			DFBRectangle rect;
			rect.x = myreg.getX0();
			rect.y = myreg.getY0();
			rect.w = myreg.getWidth();
			rect.h = myreg.getHeight();
			if (s.getIDirectFBSurface()->Blit(s.getIDirectFBSurface(), surf, &rect, theirreg.getX0(), theirreg.getY0()) != DFB_OK)
				return 1;
			return 0;
		}

		string Surface::display()
		{
			return region.display();
		}

		Renderer::Renderer()
		{
			dfb = NULL;
			ebuf = NULL;
			memset(keystates, 0, sizeof(keystates));
			memset(buttonstates, 0, sizeof(buttonstates));
			xstate = 0;
			ystate = 0;
		}

		int Renderer::init(int *argc, char ***argv)
		{
			if (DirectFBInit(argc, argv) != DFB_OK)
				return 1;
			if (DirectFBCreate(&dfb) != DFB_OK)
				return 2;
			if (dfb->SetCooperativeLevel(dfb, DFSCL_FULLSCREEN) != DFB_OK)
				return 3;
			DFBSurfaceDescription dsc;
			dsc.flags = (DFBSurfaceDescriptionFlags)(DSDESC_CAPS);
			dsc.caps = (DFBSurfaceCapabilities)(DSCAPS_PRIMARY | DSCAPS_FLIPPING);
			IDirectFBSurface *tmp;
			if (dfb->CreateSurface(dfb, &dsc, &tmp) != DFB_OK)
				return 4;
			if (dfb->EnumInputDevices(dfb, enumerateInputDevice, &devs) != DFB_OK)
				return 6;
			if (dfb->CreateInputEventBuffer(dfb, DICAPS_ALL, DFB_FALSE, &ebuf) != DFB_OK)
				return 7;
			pri = Surface(tmp);
			return 0;
		}

		int Renderer::halt()
		{
			ebuf->Release(ebuf);
			dfb->Release(dfb);
			for (map<string, IDirectFBFont *>::iterator i = fonts.begin(); i != fonts.end(); i++)
				unloadFont(i->first);
			return 0;
		}

		int Renderer::run()
		{
			bool gogogo = true;

			while(gogogo)
			{
				DFBInputEvent e;
				if (ebuf->WaitForEvent(ebuf) != DFB_OK)
					return 1;

				while(ebuf->GetEvent(ebuf, DFB_EVENT(&e)) == DFB_OK)
				{
					if (e.type == DIET_KEYPRESS)
					{
						keystates[e.key_symbol] = true;

						if (keyPressHandler(*this, e.key_symbol))
							gogogo = false;
					}
					else if (e.type == DIET_KEYRELEASE)
					{
						keystates[e.key_symbol] = false;

						if (keyReleaseHandler(*this, e.key_symbol))
							gogogo = false;
					}
					else if (e.type == DIET_AXISMOTION)
					{
						if (e.flags & DIEF_AXISABS)
						{
							if (e.axis == DIAI_X)
								xstate += (e.axisabs-e.min)/e.max;
							else if (e.axis == DIAI_Y)
								ystate += (e.axisabs-e.min)/e.max;
						}
						else if (e.flags & DIEF_AXISREL)
						{
							if (e.axis == DIAI_X)
								xstate += e.axisrel;
							else if (e.axis == DIAI_Y)
								ystate += e.axisrel;
						}

						if (motionHandler(*this, xstate, ystate))
							gogogo = false;
					}
					else if (e.type == DIET_BUTTONPRESS)
					{
						buttonstates[e.button] = true;
						if (buttonPressHandler(*this, e.button))
							gogogo = false;
					}
					else if (e.type == DIET_BUTTONRELEASE)
					{
						buttonstates[e.button] = false;
						if (buttonReleaseHandler(*this, e.button))
							gogogo = false;
					}

					return 0;
				}
			}

			return 0;
		}

		int Renderer::bindRenderHandler(renderCallback c)
		{
			renderHandler = c;
			return 0;
		}

		int Renderer::bindKeyPressHandler(keyPressCallback c)
		{
			keyPressHandler = c;
			return 0;
		}

		int Renderer::bindKeyReleaseHandler(keyReleaseCallback c)
		{
			keyReleaseHandler = c;
			return 0;
		}

		int Renderer::bindMotionHandler(motionCallback c)
		{
			motionHandler = c;
			return 0;
		}

		int Renderer::bindButtonPressHandler(buttonPressCallback c)
		{
			buttonPressHandler = c;
			return 0;
		}

		int Renderer::bindButtonReleaseHandler(buttonReleaseCallback c)
		{
			buttonReleaseHandler = c;
			return 0;
		}

		bool Renderer::getKeyState(unsigned char k)
		{
			return keystates[k];
		}

		bool Renderer::getButtonState(unsigned int b)
		{
			if (b >= 5)
				return false;
			return buttonstates[b];
		}

		int Renderer::getXState()
		{
			return xstate;
		}

		int Renderer::getYState()
		{
			return ystate;
		}

		int Renderer::loadFont(string path, int size)
		{
			DFBFontDescription dsc;
			dsc.flags = DFDESC_HEIGHT;
			dsc.height = size;
			IDirectFBFont *font;
			if (dfb->CreateFont(dfb, path.c_str(), &dsc, &font) != DFB_OK)
				return 1;
			fonts[path] = font;
			return 0;
		}

		IDirectFBFont *Renderer::getFont(string path)
		{
			if (fonts.find(path) == fonts.end())
				return NULL;
			return fonts[path];
		}

		int Renderer::getTextWidth(string path, string text)
		{
			if (fonts.find(path) == fonts.end())
				return -1;
			int w = 0;
			if (fonts[path]->GetStringWidth(fonts[path], text.c_str(), -1, &w) != DFB_OK)
				return -2;
			return w;
		}

		int Renderer::unloadFont(string path)
		{
			if (fonts.find(path) == fonts.end())
				return 1;
			fonts[path]->Release(fonts[path]);
			fonts.erase(path);
			return 0;
		}

		int Renderer::loadImage(string path, Region &r)
		{
			IDirectFBImageProvider *pro;
			IDirectFBSurface *tmp;
			DFBSurfaceDescription dsc;
			if (dfb->CreateImageProvider(dfb, path.c_str(), &pro) != DFB_OK)
				return -1;
			if (pro->GetSurfaceDescription(pro, &dsc) != DFB_OK)
				return -2;
			if (dfb->CreateSurface(dfb, &dsc, &tmp) != DFB_OK)
				return -3;
			if (pro->RenderTo(pro, tmp, NULL) != DFB_OK)
				return -4;
			surfaces[surfaces.size()] = Surface(tmp);
			r.setWidth(surfaces[surfaces.size()-1].getRegion().getWidth());
			r.setHeight(surfaces[surfaces.size()-1].getRegion().getHeight());
			pro->Release(pro);
			return surfaces.size()-1;
		}

		Surface &Renderer::getPrimary()
		{
			return pri;
		}

		IDirectFB *Renderer::getDFB()
		{
			return dfb;
		}

		int Renderer::loadSurface(Surface s)
		{
			surfaces[surfaces.size()] = s;
			return surfaces.size()-1;
		}

		vector<int> Renderer::listSurfaces()
		{
			vector<int> rtn;
			for (map<int, Surface>::iterator i = surfaces.begin(); i != surfaces.end(); i++)
				rtn.push_back(i->first);
			return rtn;
		}

		Surface &Renderer::getSurface(int s)
		{
			return surfaces[s];
		}

		int Renderer::unloadSurface(int s)
		{
			if (surfaces.find(s) == surfaces.end())
				return 1;
			surfaces.erase(s);
			return 0;
		}
	}
}
